//go:build windows
// +build windows

package win系统类

import (
	"fmt"
	"gitee.com/go_888/extends/windows/internal/windows"
	"golang.org/x/sys/windows/registry"
	"syscall"
	"time"
	"unsafe"
)

var (
	user32                     = 内部类.DLL载入("user32.dll")
	isClipboardFormatAvailable = user32.X命令("IsClipboardFormatAvailable")
	openClipboard              = user32.X命令("OpenClipboard")
	closeClipboard             = user32.X命令("CloseClipboard")
	emptyClipboard             = user32.X命令("EmptyClipboard")
	getClipboardData           = user32.X命令("GetClipboardData")
	setClipboardData           = user32.X命令("SetClipboardData")
)

var (
	kernel32     = 内部类.DLL载入("kernel32.dll")
	globalAlloc  = kernel32.X命令("GlobalAlloc")
	globalFree   = kernel32.X命令("GlobalFree")
	globalLock   = kernel32.X命令("GlobalLock")
	globalUnlock = kernel32.X命令("GlobalUnlock")
	lstrcpyW     = kernel32.X命令("lstrcpyW")
)

var (
	shell32                 = 内部类.DLL载入("shell32.dll")
	sHGetSpecialFolderPathW = shell32.X命令("SHGetSpecialFolderPathW")
)

const (
	/*
		参考 https://learn.microsoft.com/zh-cn/windows/win32/api/shldisp/ne-shldisp-shellspecialfolderconstants?redirectedfrom=MSDN
		删除了部分无效或用不到的常量. 更多参考microsoft
	*/
	X常量_系统目录_开始菜单_程序      = 2  //包含用户程序组的文件系统目录 (也是) 文件系统目录。 典型路径为 C:\Users\username\AppData\Roaming\Microsoft\Windows\Start Menu\Programs。
	X常量_系统目录_文档           = 5  //用作用户文档的通用存储库的文件系统目录。 典型路径为 C:\Users\username\Documents。
	X常量_系统目录_收藏夹          = 6  //文件系统目录，用作用户最喜爱的 URL 的通用存储库。 典型路径为 C:\Documents and Settings\username\Favorites。
	X常量_系统目录_启动           = 7  //对应于用户的启动程序组的文件系统目录。 每当任何用户在重新启动后首次登录到其配置文件时，系统就会启动这些程序。 典型路径为 C:\Users\username\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\StartUp。
	X常量_系统目录_最近使用项目       = 8  //包含用户最近使用的文档的文件系统目录。 典型路径为 C:\Users\username\AppData\Roaming\Microsoft\Windows\Recent。
	X常量_系统目录_发送到          = 9  //包含“ 发送到” 菜单项的文件系统目录。 典型路径为 C:\Users\username\AppData\Roaming\Microsoft\Windows\SendTo。
	X常量_系统目录_开始菜单         = 11 //包含 “开始” 菜单项的文件系统目录。 典型路径为 C:\Users\username\AppData\Roaming\Microsoft\Windows\Start Menu。
	X常量_系统目录_桌面           = 16 //用于物理存储桌面上显示的文件对象的文件系统目录。 不应将其与桌面文件夹本身（即虚拟文件夹）混淆。 典型路径为 C:\Documents 和 Settings\username\Desktop。
	X常量_系统目录_网络位置         = 19 //一个文件系统文件夹，其中包含“我的网络”Places虚拟文件夹中的任何链接对象。 它与表示网络命名空间根的 ssfNETWORK 不同。 典型路径为 C:\Users\username\AppData\Roaming\Microsoft\Windows\Network Shortcuts。
	X常量_系统目录_字体           = 20 //包含已安装字体的虚拟文件夹。 典型路径为 C:\Windows\Fonts。
	X常量_系统目录_文档模板         = 21 //用作文档模板通用存储库的文件系统目录。
	X常量_系统目录_所有用户_开始菜单    = 22 //文件系统目录，其中包含显示在所有用户的 “开始 ”菜单上的程序和文件夹。 典型路径为 C:\Documents and Settings\All Users\Start Menu。 仅对Windows NT系统有效。
	X常量_系统目录_所有用户_开始菜单_程序 = 23 //文件系统目录，其中包含所有用户显示在 “开始” 菜单上的常见程序组的目录。 典型路径为 C:\Documents and Settings\All Users\Start Menu\Programs。 仅对Windows NT系统有效。
	X常量_系统目录_所有用户_启动      = 24 //包含所有用户的“启动”文件夹中显示的程序的文件系统目录。 典型路径为 C:\Documents and Settings\All Users\Microsoft\Windows\Start Menu\Programs\StartUp。 仅对Windows NT系统有效。
	X常量_系统目录_所有用户_桌面      = 25 //文件系统目录，其中包含所有用户在桌面上显示的文件和文件夹。 典型路径为 C:\Documents and Settings\All Users\Desktop。 仅对Windows NT系统有效。
	X常量_系统目录_所有用户_文档      = 46 //路径 C:\Users\Public\Documents
	X常量_系统目录_数据存储_xp专用    = 26 //用作应用程序特定数据的通用存储库的文件系统目录。 典型路径为 C:\Documents and Settings\username\Application Data。
	X常量_系统目录_数据存储_Local   = 28 //文件系统目录，用作本地 (非漫游) 应用程序的数据存储库。 典型路径为 C:\Users\username\AppData\Local。
	X常量_系统目录_所有用户_收藏夹     = 31 //文件系统目录，用作所有用户共享的常用 URL 的通用存储库。 仅对Windows NT系统有效。
	X常量_系统目录_所有用户_数据存储    = 35 //所有用户的应用程序数据。 典型路径为 C:\Documents and Settings\All Users\Application Data。
	X常量_系统目录_Windows目录    = 36 //Windows目录。 这对应于 %windir% 或 %SystemRoot% 环境变量。 典型路径为 C:\Windows。
	X常量_系统目录_System32     = 37 //System文件夹。 典型路径为 C:\Windows\System32。
	X常量_系统目录_程序安装目录       = 38 //Program Files 文件夹。 典型路径为 C:\Program Files。
	X常量_系统目录_我的图片         = 39 //“我的图片”文件夹。 典型路径为 C:\Users\username\Pictures。
	X常量_系统目录_用户配置         = 40 //用户的配置文件文件夹。
	X常量_系统目录_Syswow32     = 41 //系统文件夹。 典型路径是 64 位计算机上的 C:\Windows\System32 或 C:\Windows\Syswow32。
	X常量_系统目录_程序安装目录X86    = 42 //指定 program Files (x86) 文件夹。 典型路径是 64 位计算机上的 C:\Program Files 或 C:\Program Files (x86) 。
)

type X已安装软件 struct {
	X显示名称 string    `json:"displayName"`
	X显示版本 string    `json:"displayVersion"`
	X架构   string    `json:"arch"`
	X发布者  string    `json:"publisher"`
	X安装日期 time.Time `json:"installDate"`
	// X预估大小 以 KB 为单位，该大小是由安装程序本身估计并写入注册表的，对于 MSI 文件，由 Windows Installer 进行估计。
	X预估大小  uint64 `json:"estimatedSize"`
	X联系人   string `json:"Contact"`
	X帮助链接  string `json:"HelpLink"`
	X安装来源  string `json:"InstallSource"`
	X安装位置  string `json:"InstallLocation"`
	X卸载字符串 string `json:"UninstallString"`
	X主版本号  uint64 `json:"VersionMajor"`
	X次版本号  uint64 `json:"VersionMinor"`
	X注册表键  string `json:"RegKey"`
}

func parseSoftware(rootKey registry.Key, path string) (X已安装软件, error) {
	sk, err := registry.OpenKey(rootKey, path, registry.QUERY_VALUE)
	if err != nil {
		return X已安装软件{}, fmt.Errorf("读取注册表时出错 `%s`: %s", path, err.Error())
	}
	defer sk.Close()

	dn, _, err := sk.GetStringValue("DisplayName")
	if err != nil {
		return X已安装软件{}, err
	}
	swv := X已安装软件{X显示名称: dn}

	if rootKey == registry.LOCAL_MACHINE {
		swv.X注册表键 = fmt.Sprintf(`HKLM\%s`, path)
	} else if rootKey == registry.USERS {
		swv.X注册表键 = fmt.Sprintf(`HKU\%s`, path)
	}

	dv, _, err := sk.GetStringValue("DisplayVersion")
	if err == nil {
		swv.X显示版本 = dv
	}

	pub, _, err := sk.GetStringValue("Publisher")
	if err == nil {
		swv.X发布者 = pub
	}

	id, _, err := sk.GetStringValue("InstallDate")
	if err == nil {
		swv.X安装日期, _ = time.Parse("20060102", id)
	}

	es, _, err := sk.GetIntegerValue("EstimatedSize")
	if err == nil {
		swv.X预估大小 = es
	}

	cont, _, err := sk.GetStringValue("Contact")
	if err == nil {
		swv.X联系人 = cont
	}

	hlp, _, err := sk.GetStringValue("HelpLink")
	if err == nil {
		swv.X帮助链接 = hlp
	}

	isource, _, err := sk.GetStringValue("InstallSource")
	if err == nil {
		swv.X安装来源 = isource
	}

	ilocaction, _, err := sk.GetStringValue("InstallLocation")
	if err == nil {
		swv.X安装位置 = ilocaction
	}

	ustring, _, err := sk.GetStringValue("UninstallString")
	if err == nil {
		swv.X卸载字符串 = ustring
	}

	mver, _, err := sk.GetIntegerValue("VersionMajor")
	if err == nil {
		swv.X主版本号 = mver
	}

	mnver, _, err := sk.GetIntegerValue("VersionMinor")
	if err == nil {
		swv.X次版本号 = mnver
	}
	return swv, nil
}
func getSoftwareList(baseKey, user, arch string) ([]X已安装软件, error) {
	rootKey := registry.LOCAL_MACHINE
	if user != "" {
		rootKey = registry.USERS
		baseKey = user + `\` + baseKey
	}
	k, err := registry.OpenKey(rootKey, baseKey, registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS)
	if err != nil {
		return nil, fmt.Errorf("读取注册表时出错：%s", err.Error())
	}
	defer k.Close()

	swList := make([]X已安装软件, 0)

	subkeys, err := k.ReadSubKeyNames(-1)
	if err != nil {
		return nil, fmt.Errorf("从注册表读取子项列表时出错：%s", err.Error())
	}
	for _, sw := range subkeys {
		parsed, err := parseSoftware(rootKey, baseKey+`\`+sw)
		if err != nil {
			continue
		}
		parsed.X架构 = arch
		swList = append(swList, parsed)
	}

	return swList, nil
}

func 内部_文件强制解锁(filePath string) error {
	const (
		ERROR_SUCCESS      = 0
		ERROR_MORE_DATA    = 234
		RmForceShutdown    = 1
		CCH_RM_SESSION_KEY = 256
	)
	var (
		rstrtmgr                = syscall.NewLazyDLL("Rstrtmgr.dll")
		procRmStartSession      = rstrtmgr.NewProc("RmStartSession")
		procRmRegisterResources = rstrtmgr.NewProc("RmRegisterResources")
		procRmGetList           = rstrtmgr.NewProc("RmGetList")
		procRmShutdown          = rstrtmgr.NewProc("RmShutdown")
		procRmEndSession        = rstrtmgr.NewProc("RmEndSession")
	)

	var sessionHandle uint32
	sessionKey := make([]uint16, CCH_RM_SESSION_KEY)

	ret, _, _ := procRmStartSession.Call(
		uintptr(unsafe.Pointer(&sessionHandle)),
		0,
		uintptr(unsafe.Pointer(&sessionKey[0])),
	)

	if ret != ERROR_SUCCESS {
		return fmt.Errorf("RmStartSession 失败，出错：%d", ret)
	}

	defer procRmEndSession.Call(uintptr(sessionHandle))

	filePathPtr, err := syscall.UTF16PtrFromString(filePath)
	if err != nil {
		return err
	}

	ret, _, _ = procRmRegisterResources.Call(
		uintptr(sessionHandle),
		1,
		uintptr(unsafe.Pointer(&filePathPtr)),
		0,
		0,
		0,
		0,
	)

	if ret != ERROR_SUCCESS {
		return fmt.Errorf("RmRegisterResources失败，错误为：%d", ret)
	}

	var pnProcInfoNeeded, pnProcInfo, lpdwRebootReasons uint32

	ret, _, _ = procRmGetList.Call(
		uintptr(sessionHandle),
		uintptr(unsafe.Pointer(&pnProcInfoNeeded)),
		uintptr(unsafe.Pointer(&pnProcInfo)),
		0,
		uintptr(unsafe.Pointer(&lpdwRebootReasons)),
	)

	if ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA {
		return fmt.Errorf("RmGetList失败，错误为：%d", ret)
	}

	if pnProcInfoNeeded > 0 {
		ret, _, _ = procRmShutdown.Call(
			uintptr(sessionHandle),
			RmForceShutdown,
			0,
		)

		if ret != ERROR_SUCCESS {
			return fmt.Errorf("RmShutdown失败，错误为：%d", ret)
		}
	} else {
		fmt.Println("文件未锁定")
	}

	return nil
}
